package com.ohos.ivbaranov.mfb;

import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.*;
import ohos.agp.components.element.PixelMapElement;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.media.image.PixelMap;

import java.util.List;


public class MaterialFavoriteButton extends StackLayout implements Component.ClickedListener, Component.EstimateSizeListener {

    public static final int STYLE_BLACK = 0;
    public static final int STYLE_WHITE = 1;
    public static final int STYLE_STAR = 0;
    public static final int STYLE_HEART = 1;

    private static final int DEFAULT_BUTTON_SIZE = 48;
    private static final int DEFAULT_PADDING = 12;
    private static final boolean DEFAULT_FAVORITE = false;
    private static final boolean DEFAULT_ANIMATE_FAVORITE = true;
    private static final boolean DEFAULT_ANIMATE_UNFAVORITE = false;
    private static final int DEFAULT_ROTATION_DURATION = 400;
    private static final int DEFAULT_ROTATION_ANGLE = 360;
    private static final int DEFAULT_BOUNCE_DURATION = 300;
    private static final int FAVORITE_STAR_BLACK = ResourceTable.Media_ic_star_black_24dp;
    private static final int FAVORITE_STAR_BORDER_BLACK = ResourceTable.Media_ic_star_border_black_24dp;
    private static final int FAVORITE_STAR_WHITE = ResourceTable.Media_ic_star_white_24dp;
    private static final int FAVORITE_STAR_BORDER_WHITE = ResourceTable.Media_ic_star_border_white_24dp;
    private static final int FAVORITE_HEART_BLACK = ResourceTable.Media_ic_favorite_black_24dp;
    private static final int FAVORITE_HEART_BORDER_BLACK = ResourceTable.Media_ic_favorite_border_black_24dp;
    private static final int FAVORITE_HEART_WHITE = ResourceTable.Media_ic_favorite_white_24dp;
    private static final int FAVORITE_HEART_BORDER_WHITE = ResourceTable.Media_ic_favorite_border_white_24dp;

    private OnFavoriteChangeListener mOnFavoriteChangeListener;
    private OnFavoriteAnimationEndListener mOnFavoriteAnimationEndListener;


    private Image icon;
    private Icon currentIcon;
    private OnLikeListener favoriteListener;
    private OnAnimationEndListener animationEndListener;
    private int iconSize;
    private int iconPadding;
    private int durationTime;

    private float animationScaleFactor;
    private boolean isChecked;
    private boolean isEnabled;

    private PixelMapElement mFavoriteResource;
    private PixelMapElement mNotFavoriteResource;

    String iconTypeString;

    public MaterialFavoriteButton(Context context) {
        this(context, null);
        init(context, null);
    }

    public MaterialFavoriteButton(Context context, AttrSet attrs) {
        this(context, attrs, "");
        init(context, attrs);
    }

    public MaterialFavoriteButton(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    /**
     * Does all the initial setup of the button such as retrieving all the attributes that were
     * set in xml and inflating the like button's view and initial state.
     *
     * @param context
     * @param attrs
     */
    private void init(Context context, AttrSet attrs) {
        setClickedListener(this);
        Component root = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_likeview, this, true);
        icon = (Image) root.findComponentById(ResourceTable.Id_icon);
        if (attrs.getAttr("icon_size").isPresent()) {
            iconSize = attrs.getAttr("icon_size").get().getDimensionValue();
        } else {
            iconSize = (int) Utils.dipToPixels(context, 40);
        }

        if (attrs.getAttr("mfb_rotation_duration").isPresent()) {
            durationTime = attrs.getAttr("mfb_rotation_duration").get().getDimensionValue();
        } else {
            durationTime = 1000;
        }

        if (attrs.getAttr("icon_padding").isPresent()) {
            iconPadding = attrs.getAttr("icon_padding").get().getDimensionValue();
        } else {
            iconPadding = (int) Utils.dipToPixels(context, 0);
        }

        if (attrs.getAttr("icon_type").isPresent()) {
            iconTypeString = attrs.getAttr("icon_type").get().getStringValue();
            LogUtil.error("init", "iconTypeString: " + iconTypeString);
            if (!iconTypeString.isEmpty()) {
                currentIcon = parseIconType(iconTypeString);
            }
        }
        if (attrs.getAttr("favorite_image").isPresent()) {
            mFavoriteResource = (PixelMapElement) attrs.getAttr("favorite_image").get().getElement();
            LogUtil.error("init", "mFavoriteResource: " + mFavoriteResource);
            if (mFavoriteResource != null)
                setMyFavoriteResource(mFavoriteResource);
        }

        if (attrs.getAttr("not_favorite_image").isPresent()) {
            mNotFavoriteResource = (PixelMapElement) attrs.getAttr("not_favorite_image").get().getElement();
            if (mNotFavoriteResource != null)
                setUnFavoriteDrawable(mNotFavoriteResource);
        }

        if (mFavoriteResource == null && mNotFavoriteResource == null) {
            if (currentIcon != null) {
                setIcon();
            } else {
                setIcon(IconType.Heart);
            }
        }

        if (attrs.getAttr("is_enabled").isPresent()) {
            setEnabled(attrs.getAttr("is_enabled").get().getBoolValue());
        } else {
            setEnabled(true);
        }

        if (attrs.getAttr("favorite").isPresent()) {
            setFavorite(attrs.getAttr("favorite").get().getBoolValue());
        } else {
            setFavorite(false);
        }

        if (attrs.getAttr("anim_scale_factor").isPresent()) {
            setAnimationScaleFactor(attrs.getAttr("anim_scale_factor").get().getFloatValue());
        } else {
            setAnimationScaleFactor(3);
        }

    }


    /**
     * This triggers the entire functionality of the button such as icon changes,
     * animations, listeners etc.
     *
     * @param component
     */
    private boolean isShowOrNot = false;
    private boolean mFavorite;

    @Override
    public void onClick(Component component) {

        if (!isEnabled) {
            return;
        }
        isChecked = !isChecked;
        if (isChecked) {
            new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
                @Override
                public void run() {
                    icon.setImageElement(mFavoriteResource);
                }
            }, durationTime);
        } else {

            if (iconTypeString.equals("thumb")) {
                new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
                    @Override
                    public void run() {
                        icon.setImageElement(mNotFavoriteResource);
                    }
                }, durationTime);
            } else {
                icon.setImageElement(mNotFavoriteResource);
            }
        }

        if (favoriteListener != null) {
            if (isChecked) {
                favoriteListener.favorite(this);
            } else {
                favoriteListener.unFavorite(this);
            }
        }

        if (isChecked) {
            mFavorite = true;
            if (iconTypeString.equals("heart")) {
                AnimatorProperty animatorProperty = icon.createAnimatorProperty();
                if (!isShowOrNot) {
                    isShowOrNot = true;
                    animatorProperty.rotate(360).setDuration(durationTime);
                } else {
                    isShowOrNot = false;
                    animatorProperty.rotate(720).setDuration(durationTime);
                }
                animatorProperty.start();

                AnimatorProperty animator = icon.createAnimatorProperty();
                animator.scaleXFrom(0f);
                animator.scaleYFrom(0f);
                animator.scaleX(1f);
                animator.scaleY(1f);
                animator.setDuration(500);
                animator.start();

            } else if (iconTypeString.equals("star")) {
                AnimatorProperty animatorProperty = icon.createAnimatorProperty();
                if (!isShowOrNot) {
                    isShowOrNot = true;
                    animatorProperty.rotate(72).setDuration(durationTime);
                } else {
                    isShowOrNot = false;
                    animatorProperty.rotate(144).setDuration(durationTime);
                }
                animatorProperty.start();

                AnimatorProperty animator = icon.createAnimatorProperty();
                animator.scaleXFrom(0f);
                animator.scaleYFrom(0f);
                animator.scaleX(1f);
                animator.scaleY(1f);
                animator.setDuration(500);
                animator.start();
            }

        } else {
            mFavorite = false;
        }
        if (iconTypeString.equals("thumb")) {
            AnimatorProperty animatorProperty = icon.createAnimatorProperty();
            if (!isShowOrNot) {
                isShowOrNot = true;
                animatorProperty.rotate(360).setDuration(durationTime);
            } else {
                isShowOrNot = false;
                animatorProperty.rotate(0).setDuration(durationTime);
            }
            animatorProperty.start();
        }


        if (mOnFavoriteAnimationEndListener != null) {
            mOnFavoriteAnimationEndListener.onAnimationEnd(MaterialFavoriteButton.this, mFavorite);
        }
        if (mOnFavoriteChangeListener != null) {
            mOnFavoriteChangeListener.onFavoriteChanged(this, mFavorite);
        }
    }

    /**
     * This drawable is shown when the button is a favorite state.
     *
     * @param resId
     */
    public void setFavoriteDrawableRes(int resId) {
        icon.setPixelMap(resId);
        if (iconSize != 0) {
            PixelMap pixelMap = icon.getPixelMap();
            mFavoriteResource = new PixelMapElement(pixelMap);
            mFavoriteResource.setBounds(0, 0, iconSize, iconSize);
            icon.setWidth(iconSize);
            icon.setHeight(iconSize);
            icon.setImageElement(mFavoriteResource);
        }
    }

    /**
     * This drawable is shown when the button is in a favorite state.
     *
     * @param mFavoriteResource
     */
    public void setMyFavoriteResource(PixelMapElement mFavoriteResource) {
        if (iconSize != 0) {
            mFavoriteResource.setBounds(0, 0, iconSize, iconSize);
            icon.setWidth(iconSize);
            icon.setHeight(iconSize);
            icon.setPadding(iconPadding, iconPadding, iconPadding, iconPadding);
        }
        this.mFavoriteResource = mFavoriteResource;

        if (isChecked) {
            icon.setImageElement(mFavoriteResource);
        }
    }

    /**
     * This drawable will be shown when the button is in on unFavorite state.
     *
     * @param resId
     */
    public void setUnFavoriteDrawableRes(int resId) {
        icon.setPixelMap(resId);
        if (iconSize != 0) {
            PixelMap pixelMap = icon.getPixelMap();
            mNotFavoriteResource = new PixelMapElement(pixelMap);
            mNotFavoriteResource.setBounds(0, 0, iconSize, iconSize);
            icon.setWidth(iconSize);
            icon.setHeight(iconSize);
            icon.setPadding(iconPadding, iconPadding, iconPadding, iconPadding);
            icon.setImageElement(mNotFavoriteResource);
        }
    }

    /**
     * This drawable will be shown when the button is in on unFavorite state.
     *
     * @param unFavoriteDrawable
     */
    public void setUnFavoriteDrawable(PixelMapElement unFavoriteDrawable) {
        if (iconSize != 0) {
            unFavoriteDrawable.setBounds(0, 0, iconSize, iconSize);
            icon.setWidth(iconSize);
            icon.setHeight(iconSize);
        }
        this.mNotFavoriteResource = unFavoriteDrawable;

        if (!isChecked) {
            icon.setImageElement(this.mNotFavoriteResource);
        }
    }

    /**
     * Sets one of the three icons that are bundled with the library.
     *
     * @param currentIconType
     */
    public void setIcon(IconType currentIconType) {
        currentIcon = parseIconType(currentIconType);
        setFavoriteDrawableRes(currentIcon.getOnIconResourceId());
        setUnFavoriteDrawableRes(currentIcon.getOffIconResourceId());
    }

    public void setIcon() {
        setFavoriteDrawableRes(currentIcon.getOnIconResourceId());
        setUnFavoriteDrawableRes(currentIcon.getOffIconResourceId());
    }

    /**
     * Sets the size of the drawable/icon that's being used. The views that generate
     * the like effect are also updated to reflect the size of the icon.
     *
     * @param iconSize
     */

    public void setIconSizeDp(int iconSize) {
        setIconSizePx((int) Utils.dipToPixels(getContext(), (float) iconSize));
    }

    /**
     * Sets the size of the drawable/icon that's being used. The views that generate
     * the like effect are also updated to reflect the size of the icon.
     *
     * @param iconSize
     */
    public void setIconSizePx(int iconSize) {
        this.iconSize = iconSize;
        this.mNotFavoriteResource.setBounds(0, 0, iconSize, iconSize);
        this.mFavoriteResource.setBounds(0, 0, iconSize, iconSize);
        icon.setWidth(iconSize);
        icon.setHeight(iconSize);
    }

    /**
     * * Parses the specific icon based on string
     * version of its enum.
     * These icons are bundled with the library and
     * are accessed via objects that contain their
     * resource ids and an enum with their name.
     *
     * @param iconType
     * @return Icon
     */
    private Icon parseIconType(String iconType) {
        List<Icon> icons = Utils.getIcons();

        for (Icon icon : icons) {
            if (icon.getIconType().name().toLowerCase().equals(iconType.toLowerCase())) {
                return icon;
            }
        }

        throw new IllegalArgumentException("Correct icon type not specified.");
    }

    /**
     * Parses the specific icon based on it's type.
     * These icons are bundled with the library and
     * are accessed via objects that contain their
     * resource ids and an enum with their name.
     *
     * @param iconType
     * @return
     */
    private Icon parseIconType(IconType iconType) {
        List<Icon> icons = Utils.getIcons();

        for (Icon icon : icons) {
            if (icon.getIconType().equals(iconType)) {
                return icon;
            }
        }

        throw new IllegalArgumentException("Correct icon type not specified.");
    }

    /**
     * Listener that is triggered once the
     * button is in a favorite or unFavorite state
     *
     * @param likeListener
     */
    public void setOnLikeListener(OnLikeListener likeListener) {
        this.favoriteListener = likeListener;
    }

    /**
     * Listener that is triggered once the
     * button animation is completed
     *
     * @param animationEndListener
     */
    public void setOnAnimationEndListener(OnAnimationEndListener animationEndListener) {
        this.animationEndListener = animationEndListener;
    }

    /**
     * Sets the initial state of the button to favorite
     * or unFavorite.
     *
     * @param status
     */
    public void setFavorite(Boolean status) {
        if (status) {
            isChecked = true;
            icon.setImageElement(mFavoriteResource);
        } else {
            isChecked = false;
            icon.setImageElement(mNotFavoriteResource);
        }
    }

    /**
     * Returns current like state
     *
     * @return current like state
     */
    public boolean isFavorite() {
        return isChecked;
    }

    @Override
    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
    }

    /**
     * Sets the factor by which the dots should be sized.
     */
    public void setAnimationScaleFactor(float animationScaleFactor) {
        this.animationScaleFactor = animationScaleFactor;
    }

    public interface OnAnimationEndListener {
        void onAnimationEnd(MaterialFavoriteButton likeButton);
    }

    public interface OnLikeListener {
        void favorite(MaterialFavoriteButton likeButton);

        void unFavorite(MaterialFavoriteButton likeButton);
    }

    private int width = 0;
    private int height = 0;

    @Override
    public boolean onEstimateSize(int i, int i1) {
        int wMode = EstimateSpec.getMode(i);
        int hMode = EstimateSpec.getMode(i1);
        if (width != 0 && height != 0) {
            setEstimatedSize(EstimateSpec.getSizeWithMode(width, wMode),
                    EstimateSpec.getSizeWithMode(height, hMode));
            return true;
        }
        return false;
    }

    /**
     * Interface definition for a callback to be invoked when the favorite state is changed.
     */
    public interface OnFavoriteChangeListener {
        /**
         * Called when the favorite state is changed.
         *
         * @param buttonView the button view whose state has changed
         * @param favorite   the favorite state
         */
        void onFavoriteChanged(MaterialFavoriteButton buttonView, boolean favorite);
    }

    /**
     * Set a listener will be called when the favorite state is changed.
     *
     * @param listener the {@link MaterialFavoriteButton.OnFavoriteChangeListener} will be called
     */
    public void setOnFavoriteChangeListener(OnFavoriteChangeListener listener) {
        mOnFavoriteChangeListener = listener;
    }

    /**
     * Interface definition for a callback to be invoked when the favorite animation ends.
     */
    public interface OnFavoriteAnimationEndListener {
        /**
         * Called when the favorite animation ended.
         *
         * @param buttonView the button view whose animation ended
         * @param favorite   the favorite state
         */
        void onAnimationEnd(MaterialFavoriteButton buttonView, boolean favorite);
    }

    /**
     * Set a listener will be called when the favorite state is changed.
     *
     * @param listener the {@link MaterialFavoriteButton.OnFavoriteAnimationEndListener} will be
     *                 called
     */
    public void setOnFavoriteAnimationEndListener(OnFavoriteAnimationEndListener listener) {
        mOnFavoriteAnimationEndListener = listener;
    }

}
